기계음·영어 발화 감지 로직 분석
마지막 업데이트 2026-07-22
apps/web — use-ai-audio-noise-guard.ts / use-ai-english-guard.ts / use-monitor-session.ts ·
2026-05-20
1. 전체 감지 시스템 개요
PPI는 AI(핑퐁이)가 비정상 오디오를 출력할 때 이를 감지하는 두 개의 독립적인 방어 시스템을 운영합니다.
🔊 기계음 감지 Noise Guard
AI 오디오 스트림의 진폭 변동계수(CV) 와 영교차율(ZCR) 을 분석하여 기계적으로 균일한 파형을 감지합니다.
파일: hooks/use-ai-audio-noise-guard.ts
🗣️ 영어 발화 감지 MFCC Guard
AI 오디오 스트림에서 MFCC 특징을 추출해 사전 녹음된 영어 발음 템플릿과 코사인 유사도 를 비교합니다.
파일: hooks/use-ai-english-guard.ts
2. 엔드-투-엔드 데이터 흐름
Guest (아동 브라우저)
AI 오디오 스트림 수신
OpenAI Realtime API → aiAudioStream (MediaStream)
↓
기계음 감지 (병렬 실행)
useAiAudioNoiseGuard
50ms 주기 AnalyserNode 분석 · CV + ZCR 판정 · 2-stage 재검증
+
영어 발화 감지 (병렬 실행)
useAiEnglishGuard
ScriptProcessorNode → MFCC 추출 → 코사인 유사도 → 패턴 시퀀스 매칭
↓ 감지 시
Guest Action (기계음)
cancelResponse() 호출 + sendIssueReport()
소켓 이벤트: GUEST_ISSUE_REPORT { issueText: "AI 오디오 기계음 감지 - 자동 중단", icon: "🔊" }
|
Guest Action (영어)
cancelResponse() 호출 + emitMfccEventToMonitor()
소켓 이벤트: GUEST_MFCC_LOG_EVENT { event: { type: "english_detected", sim, phrase, consecutive } }
↓ Socket.io 전달
Socket Server
GROUP_ALERT_BROADCAST 릴레이
같은 그룹 내 다른 세션 모니터들에게도 이벤트 브로드캐스트
↓
Monitor (치료사 브라우저) — use-monitor-session.ts
handleGuestIssueReport / handleMfccLogEvent
이벤트 수신 → AlertType 매핑 → setAlerts + useMonitorDataStore.addAlert
↓ 사이드이펙트
모니터 UI
Alert 패널 + Toast 알림
issueToast (3s 자동 닫힘) · alerts[] 배열 추가
+
MFCC 로그 (영어 감지 시)
turn_end 수신 → S3 업로드
JSONL blob → /api/session-logs/{turnId}/mfcc-upload-url → S3 PUT → PATCH mfccS3Key
3. 기계음 감지 알고리즘 상세
파일: apps/web/hooks/use-ai-audio-noise-guard.ts
3-1. 핵심 파라미터
상수 값 설명
ANALYSIS_INTERVAL_MS 50 분석 주기 (ms)
RING_BUFFER_SIZE 14 1차 판정 링 버퍼 크기 (14프레임 = 700ms)
NOISE_FRAME_THRESHOLD 10 링 버퍼 중 기계음 판정 필요 프레임 수 (≈71%)
CV_THRESHOLD 0.35 RMS 변동계수 임계값 (이하 → 기계음 의심)
ZCR_MIN_THRESHOLD 0.04 영교차율 최소값 (이 이상이어야 기계음 판정 유효)
RMS_SILENCE_FLOOR_DB –50 dB 무음 필터 기준 (이하는 판정 제외)
GRACE_PERIOD_MS 500 AI 발화 시작 후 판정 유예 구간
REVERIFY_DURATION_MS 200 1차 감지 후 2차 재검증 기간
REVERIFY_FRAMES 4 재검증 프레임 수
REVERIFY_THRESHOLD 3 재검증 통과 필요 기계음 프레임 수
COOLDOWN_MS 10,000 연속 감지 억제 쿨다운 (ms)
RMS_WINDOW_SIZE 8 CV 계산용 RMS 슬라이딩 윈도우 크기 (400ms)
FFT_SIZE 2048 AnalyserNode FFT 크기
3-2. 판정 파이프라인
무음 필터
50ms 마다 AnalyserNode에서 Float32Array 읽음. RMS가 –50 dB 미만이면 해당 프레임 건너뜀.
RMS 슬라이딩 윈도우 (8프레임)
최근 8프레임(400ms)의 dB RMS를 linear 변환 후 배열에 유지. CV 계산에 사용.
CV 계산 (변동계수)
CV = std(linear_rms) / mean(linear_rms)
정상 음성 CV ≈ 0.85 · 기계음 CV ≈ 0.27. 임계값 0.35 이하이면 기계음 후보.
ZCR 보조 조건
영교차율이 0.04 이상이어야 판정 유효. 너무 매끈한 저주파 구간의 오탐을 보완.
1차 판정 — 링 버퍼
14프레임 원형 버퍼에 기계음 여부 Boolean 기록. 10/14 이상이면 후보 감지 .
2차 재검증 (200ms / 4프레임)
후보 감지 후 200ms 추가 관찰. 4프레임 중 3프레임 이상 기계음이면 확정 감지 . 아니면 링 버퍼 초기화 후 재시작.
쿨다운 체크 (10초)
마지막 감지로부터 10초 이내면 콜백 호출 억제.
onNoiseDetected() 콜백
cancelResponse() 호출 → AI 응답 즉시 중단.
guestSocket.sendIssueReport("AI 오디오 기계음 감지 - 자동 중단", "🔊") 전송.
설계 의도: 리뷰 반영으로 1차 감지 즉시 중단 대신 200ms 재검증을 추가. 짧은 무음 dip이나 발화 초기 과도기(500ms 유예)에서 발생하는 오탐을 줄이기 위한 구조.
4. 영어 발화 감지 알고리즘 상세 (MFCC)
파일: apps/web/hooks/use-ai-english-guard.ts + apps/web/lib/mfcc.ts
4-1. MFCC 추출 파라미터
파라미터 값 설명
SAMPLE_RATE 16,000 Hz 처리 샘플레이트 (입력을 여기로 다운샘플)
FRAME_SIZE 400 samples 25ms @ 16kHz
HOP_SIZE 160 samples 10ms 스텝
FFT_SIZE 512 Radix-2 Cooley-Tukey FFT
NUM_MEL_FILTERS 26 Mel 필터뱅크 수
NUM_MFCC 13 MFCC 계수 수 (c0~c12)
PRE_EMPHASIS 0.97 고주파 강조 계수
4-2. 슬라이딩 윈도우 설정
상수 값 설명
WINDOW_SAMPLES_16K 4,800 300ms @ 16kHz 분석 윈도우
HOP_SAMPLES_16K 2,400 150ms 슬라이딩 간격
SCRIPT_PROCESSOR_BUFFER_SIZE 4,096 ScriptProcessorNode 버퍼
MIN_RMS_ENERGY 0.005 무음 구간 스킵 기준
GRACE_PERIOD_MS 500 AI 발화 시작 후 유예 구간
COOLDOWN_MS 10,000 재감지 억제 쿨다운
TOP_N_TEMPLATES 3 검증 로그에 포함할 상위 유사도 템플릿 수
MAX_SILENCE_HOPS_FOR_RESET 5 연속 무음 hop 한계 (≈750ms 까지 커서 유지)
PATTERN_RULE_MISS_TOLERANCE 2 패턴 시퀀스 miss 허용 횟수
4-3. 감지 알고리즘 — 두 가지 모드
모드 A: 패턴 시퀀스 매칭 주 감지 방식
mfccPatternRules 존재 시 활성화
서버에서 내려온 MfccPatternRule[]로 순서 있는 템플릿 시퀀스 정의
각 rule에 cursor 유지
ruleCursorsRef: Map<ruleId, cursor> — 현재 몇 번째 스텝 대기 중인지 추적
스텝 매칭 조건
maxSimilarity ≥ 전역 threshold AND bestTemplateIdx === 기대 tldx AND sim ≥ 스텝별 threshold
miss 허용 (PATTERN_RULE_MISS_TOLERANCE = 2)
2회 이상 miss 시 cursor 리셋. 음절 경계 dip에서 시퀀스가 중단되지 않도록 보완.
시퀀스 완성 + stopSpeech=true
onEnglishDetected(tldxSequence) 호출 → AI 응답 즉시 중단
모드 B: Consecutive 매칭 로그 전용
패턴 규칙 없을 때 fallback
연속 매칭 카운터(consecutiveMatchCount)로 판단
같은 phraseGroup 내에서만 누적
그룹이 바뀌거나 offset이 감소하면 카운터 리셋
threshold 도달 시 로그만 남김
※ 현재는 action 없음 — 패턴 시퀀스 감지가 주 트리거
4-4. MFCC 추출 파이프라인
PCM 입력 (임의 SR)
→
16kHz 다운샘플 (선형 보간)
→
Pre-emphasis (α=0.97)
→
Hamming 윈도우 (25ms)
→
FFT-512 (Radix-2)
→
파워 스펙트럼
→
Mel 필터뱅크 26개
→
log(E)
→
DCT-II → 13 계수
→
c0 제거 후 코사인 유사도
c0(에너지 계수) 제거 이유: MFCC의 첫 번째 계수(c0)는 모든 음성에서 비슷한 에너지 레벨을 가져 오탐을 유발합니다. c1~c12만 비교해 음색 유사도를 더 정확하게 측정합니다.
5. 모니터 측 MFCC 로그 수집 시스템
파일: entities/monitor-session/model/use-monitor-session.ts + hooks/use-mfcc-log-collector.ts
5-1. 턴 버퍼 구조
버퍼 관련 상수
상수 값
MONITOR_MFCC_MAX_EVENTS_PER_TURN 2,000
MFCC_TURN_END_FLUSH_GRACE_MS 1,500 ms
MFCC_TURN_END_FLUSH_INFLIGHT_POLL_MS 200 ms
MFCC_TURN_END_FLUSH_MAX_WAIT_MS 6,000 ms
핵심 Ref 변수
변수 설명
currentTurnBufferRef 현재 턴 이벤트 버퍼 (MfccLogEvent[])
inActiveTurnRef ai_speaking_start ~ turn_end 구간 flag
currentTurnUserIdRef 현재 턴 소유 userId
pendingTurnFlushRef grace 지연 flush 타이머 정보
mfccUploadInProgressRef 중복 업로드 방지 Set<turnId>
5-2. 이벤트 타입 분류 (MfccLogEvent)
type 설명 소비 방식
session_start 세션 시작 메타 정보 버퍼 append
session_end 세션 종료 버퍼 append
ai_speaking_start AI 발화 시작 → 새 턴 시작 이전 미완결 버퍼 flush + inActiveTurn=true
ai_speaking_stop AI 발화 중단 버퍼 append
transcript 발화 전사 스냅샷 버퍼 append
mfcc_sample 300ms 윈도우 MFCC + 코사인 유사도 버퍼 append
match_candidate 연속 매칭 후보 (로그용) 버퍼 append
english_detected 영어 발화 최종 감지 버퍼 append + onMfccEnglishDetected 콜백
pattern_step 패턴 시퀀스 진행 스텝 버퍼 append
reset consecutive 카운터 리셋 이유 버퍼 append
turn_end 턴 종료 → grace flush 스케줄 grace 타이머 시작 후 S3 업로드
5-3. turn_end 처리 — Grace Flush 흐름
소켓 이벤트
turn_end 수신 (GUEST_MFCC_LOG_EVENT)
↓
처리 1
이전 pendingFlush 즉시 실행
이전 턴이 grace 중이었다면 clearTimeout 후 즉시 flushTurnMfcc 호출
→
처리 2
Grace 타이머 시작 (1,500ms)
inActiveTurn 유지 — 그 사이 도착하는 이벤트도 현재 버퍼에 수용
↓ 1,500ms 후 (또는 다음 ai_speaking_start 시 즉시)
Inflight 폴링
getInflightLidDetection() 체크
Whisper 추론이 in-flight면 200ms 간격으로 재폴링. 최대 6,000ms 하드 캡.
↓ inflight 완료 또는 하드 캡
S3 업로드
uploadTurnMfccLog
/api/session-logs/{turnId}/mfcc-upload-url → Presigned URL → S3 PUT (JSONL)
→
DB 연결
registerTurnMfccLog
/api/session-logs/{turnId}/mfcc PATCH — mfccS3Key, mfccRange 업데이트
5-4. mfccRange 요약 메타 (summarizeTurnMfcc)
필드 설명
startMs / endMs 턴 이벤트 타임스탬프 범위
eventCount 총 이벤트 수
sampleCount mfcc_sample 이벤트 수
detectedEnglish english_detected 이벤트 존재 여부
detectedByMfcc detectedEnglish와 동일
maxSim mfcc_sample 또는 english_detected 중 최대 코사인 유사도
matchedPatternSequence pattern_step에서 완성된 tldx 시퀀스 배열
6. 모니터 수신 처리 — Alert 생성
6-1. 이벤트 → AlertType 매핑
🔊
GUEST_ISSUE_REPORT → issueText.includes("기계음 감지")
AlertType.AI_MECHANICAL_NOISE · 우선순위 P0
즉시 토스트
🗣️
GUEST_ISSUE_REPORT → issueText.includes("영어 발화 감지")
AlertType.AI_ENGLISH_DETECTED · 우선순위 P0
즉시 토스트
🧩
GUEST_ISSUE_REPORT → issueText.includes("영어 발화 패턴 감지")
AlertType.MFCC_PATTERN_MATCHING · 우선순위 P0
즉시 토스트
🆘
GUEST_ISSUE_REPORT → 기타 issueText
AlertType.CHILD_HELP_REQUEST · 우선순위 P0
즉시 토스트
6-2. MFCC english_detected 이벤트 처리 흐름
handleMfccLogEvent 수신
roomId 필터링 후 event.type 분기
event.type === "ai_speaking_start"
inActiveTurn = true. 이전 pendingFlush가 있으면 즉시 실행.
event.type === "english_detected"
onMfccEnglishDetectedRef.current({ sim, phrase, consecutive }) 콜백 호출. 버퍼에도 append.
event.type === "turn_end"
Grace 타이머 스케줄. inActiveTurn = false 는 flush 완료 후 처리.
6-3. 중복 감지 방지 메커니즘
mountedMonitorRoomIds
카드뷰에서 같은 그룹의 여러 세션이 동시 마운트될 때 GROUP_ALERT_BROADCAST 중복 처리를 방지하는 모듈-레벨 Set.
원본 roomId를 담당하는 카드가 이미 직접 처리하므로 브로드캐스트 경로에서 skip.
mfccUploadInProgressRef
Set<turnId>로 동일 turnId에 대한 동시 S3 업로드를 방지. flushTurnMfcc 진입 시 체크하고, finally에서 삭제.
7. 연결 해제 시 버퍼 정리
guest-disconnected / peer-left 수신 시: pendingTurnFlushRef 타이머를 clearTimeout하고 currentTurnBufferRef를 비웁니다. turn_end 없이 끊어진 미완결 턴은 S3에 업로드하지 않고 폐기합니다 (튜닝 가치가 낮고, 다음 세션 시작 시 버퍼 오염 방지 목적).
컴포넌트 unmount 시: pendingTurnFlushRef 타이머만 clearTimeout합니다. 남은 버퍼는 폐기되며 S3 업로드 호출을 생략합니다.
8. 시스템 요약
항목
기계음 감지 (NoiseGuard)
영어 발화 감지 (MFCC Guard)
감지 원리
RMS CV < 0.35 + ZCR 조건
MFCC 코사인 유사도 + 패턴 시퀀스
분석 주기
50ms (AnalyserNode)
150ms 슬라이딩 hop (ScriptProcessor)
감지 확인 방식
2-stage (링 버퍼 + 재검증 200ms)
패턴 시퀀스 완성 (miss 2회 허용)
AI 응답 중단
cancelResponse() 즉시 호출
onEnglishDetected → cancelResponse()
소켓 이벤트
GUEST_ISSUE_REPORT
GUEST_MFCC_LOG_EVENT
모니터 AlertType
AI_MECHANICAL_NOISE (P0)
AI_ENGLISH_DETECTED / MFCC_PATTERN_MATCHING (P0)
로그 저장
없음
JSONL → S3 (turn_end grace flush)
쿨다운
10초
10초
발화 시작 유예
500ms
500ms
생성: 2026-05-20 · 분석 대상: apps/web (develop 브랜치) ·
주요 파일: use-ai-audio-noise-guard.ts, use-ai-english-guard.ts, use-monitor-session.ts, use-mfcc-log-collector.ts, lib/mfcc.ts